In [1]:
import pandas as pd 
import numpy as np 
from datetime import timedelta
import altair as alt
import contextlib
import sys
import os
alt.data_transformers.enable("vegafusion")
Out[1]:
DataTransformerRegistry.enable('vegafusion')
In [2]:
from gridSavers import (
    optimize_battery_schedule_df_flexible_end,
    run_weekly_optimization_with_daily_usage,
    simulate_all_nodes, 
    prepare_visualization, 
    plot_altair_separate
)

Grid Savers: Potential Benifits to Utilize V2G¶

Using a Vehicle to Grid (V2G) system, utilities can benifit from lower residential demands during peak hours, which cuts the cost of purchasing electricity in the wholesale power market. In this report, we are interested in simulating the implementastion of such technology.

Assumptions¶

We are assuming:

  • The capacity of hardware, software, and regulation is supportive for this technology to implement
  • The reduce in demand will not effect wholesale electricity pricing

Data¶

  • Local Marginal Price (LMP) of the Day Ahead Market (DAM) in CAISO on May 5th and May6th.
    • Retrieved throught CAISO's OASIS Website
    • Preprocessed with preprocess.ipynb
    • Loaded as allNode in this notebook
  • LMP of DAM at placeholder
    • Retrieved throught ...
    • Loaded as placeholder in this notebook
  • On average, American drivers drive 30.1 miles everyday.
  • For the charger side: We are using the Tesla Wall Connector that has a 11.5 kW/h output, which is equal to 44 miles. We assume the power input is the same rate.
  • For the EV/ battery side: Below is the Useable Battery Capacity of Full Electric Vehicles (Tesla)
Model Useable Battery Capacity (kWh)
S Dual Motor 95
S Plaid 95
X Dual Motor 95
X Plaid 95
Y Long Range AWD 75
Y Long Range AWD Launch Series 75
Y Long Range RWD 75
3 Performance 75
3 Long Range Dual Motor 75
3 Long Range RWD 75
Y RWD 60
3 57.5

EDA¶

In [3]:
allNode = pd.read_csv('data/0506pre.csv')
allNode['INTERVALSTARTTIME'] = pd.to_datetime(allNode['INTERVALSTARTTIME'])
allNode.head() 
Out[3]:
OPR_DT NODE MW INTERVALSTARTTIME INTERVALENDTIME Hour_Label
0 2025-05-05 AFPR_1_TOT_GEN-APND 34.41489 2025-05-05 00:00:00 2025-05-05 01:00:00 00:00-01:00
1 2025-05-05 AFPR_1_TOT_GEN-APND 34.37762 2025-05-05 01:00:00 2025-05-05 02:00:00 01:00-02:00
2 2025-05-05 AFPR_1_TOT_GEN-APND 34.11542 2025-05-05 02:00:00 2025-05-05 03:00:00 02:00-03:00
3 2025-05-05 AFPR_1_TOT_GEN-APND 33.97113 2025-05-05 03:00:00 2025-05-05 04:00:00 03:00-04:00
4 2025-05-05 AFPR_1_TOT_GEN-APND 34.23300 2025-05-05 04:00:00 2025-05-05 05:00:00 04:00-05:00
In [4]:
chart_allNode = alt.Chart(allNode).mark_line(interpolate='step-after').encode(
    x=alt.X('INTERVALSTARTTIME:T', title='Time Interval'),
    y=alt.Y('MW:Q', title='Price ($/MWh)'),
    color='NODE:N',
    tooltip=['NODE', 'Hour_Label', 'MW']
).properties(
    title='Day-Ahead LMP ($/MWh) Over Time by Node',
    width=800,
    height=400
).interactive()

chart_allNode
Out[4]:

From the plot, we found high variability across different nodes. We want to find the ones with higher profitability to illustrate the benifit of V2G implementation.

V2G Simulation¶

Case Specifications:

  • The resident has an EV with a battery of 95 kWh
  • The resident has a charger of 11.5 kWh/h output
  • The resident uses the EV from 8:00 - 18:00 and is participated in V2G program controled by the utility from 18:00 - 8:00
  • The resident travels 30.1 miles a day
In [5]:
with contextlib.redirect_stdout(open(os.devnull, 'w')):
    node_profit_df = simulate_all_nodes(allNode)
In [6]:
# node_profit_df.to_csv("node_profit_summary.csv", index=False)
node_profit_df.head(5)
Out[6]:
NODE Total Profit ($)
0 POD_ADLIN_1_UNITS-APND 4.982919
1 SLAP_PGNC-APND 4.934240
2 PGNC_1_PDRP19-APND 4.916442
3 PGNC_1_PDRP17-APND 4.916442
4 PGNC_1_PDRP01-APND 4.916442

Node POD_ADLIN_1_UNITS-APND appeared to have the most profitability with the one day of simulation. Let's retrieve more data from this location.

Below is the LMP of node POD_ADLIN_1_UNITS-APND from 2025-04-07 to 2025-05-06.

In [7]:
monthly = pd.read_csv('data/monthlypre.csv')
monthly['INTERVALSTARTTIME'] = pd.to_datetime(monthly['INTERVALSTARTTIME'])
monthly.head()
Out[7]:
OPR_DT NODE MW INTERVALSTARTTIME INTERVALENDTIME Hour_Label
0 2025-04-07 POD_ADLIN_1_UNITS-APND 4.79520 2025-04-07 00:00:00 2025-04-07 01:00:00 00:00-01:00
1 2025-04-07 POD_ADLIN_1_UNITS-APND -6.72152 2025-04-07 01:00:00 2025-04-07 02:00:00 01:00-02:00
2 2025-04-07 POD_ADLIN_1_UNITS-APND -4.41947 2025-04-07 02:00:00 2025-04-07 03:00:00 02:00-03:00
3 2025-04-07 POD_ADLIN_1_UNITS-APND -7.62245 2025-04-07 03:00:00 2025-04-07 04:00:00 03:00-04:00
4 2025-04-07 POD_ADLIN_1_UNITS-APND -7.82635 2025-04-07 04:00:00 2025-04-07 05:00:00 04:00-05:00
In [8]:
chart_month = alt.Chart(monthly).mark_line(interpolate='step-after').encode(
    x='INTERVALSTARTTIME:T',
    y='MW:Q',
    tooltip=['Hour_Label', 'MW']
).properties(
    title='Day-Ahead LMP ($/MWh) for Node- AFPR_1_TOT_GEN-APND',
    width=800,
    height=400
).interactive()

chart_month
Out[8]:
In [9]:
monthly_result = run_weekly_optimization_with_daily_usage(
    monthly,
    initial_soc=66.5,
    daily_miles=30.1,
    miles_per_kwh=4.0  # ← adjustable
)

print("Cumulative profit:", round(monthly_result["Profit ($)"].sum(), 2))
Skipping 2025-04-06: Incomplete data (8 hours)
Skipping 2025-05-06: Incomplete data (6 hours)
Cumulative profit: 27.99
In [11]:
df_all = prepare_visualization(monthly_result, monthly)
chart = plot_altair_separate(df_all)
chart
Out[11]:

Conclusion:¶

  • This simulation calculated how much utilities can benifit from arbitrage
  • This simulation also provide a reference point

Limitations¶

  • This simulation did not consider potential costs to implement such technology
  • This simulation also did not consider the Real-Time Market
  • There is a lack of information on utilities' purchase at each node, therefore